Skip to content

feat: Password protected public share#1922

Open
FabianDevel wants to merge 17 commits intomainfrom
password-protected-public-share
Open

feat: Password protected public share#1922
FabianDevel wants to merge 17 commits intomainfrom
password-protected-public-share

Conversation

@FabianDevel
Copy link
Contributor

Add the possibility to open a password protected public share directly in the application instead of going to the browser.

@FabianDevel FabianDevel added enhancement New feature or request feature A new functionality is added to the product labels Jan 16, 2026
@FabianDevel FabianDevel force-pushed the password-protected-public-share branch from 58b1095 to 679cc1f Compare January 20, 2026 08:43
@FabianDevel FabianDevel marked this pull request as draft January 20, 2026 09:25
@FabianDevel FabianDevel force-pushed the password-protected-public-share branch from 679cc1f to 0482e15 Compare January 28, 2026 14:26
@FabianDevel FabianDevel force-pushed the password-protected-public-share branch from 0482e15 to 671e192 Compare February 5, 2026 10:41
@benjaminVadon benjaminVadon force-pushed the password-protected-public-share branch from 671e192 to f553c07 Compare February 19, 2026 12:34
@benjaminVadon benjaminVadon self-assigned this Feb 20, 2026
@benjaminVadon benjaminVadon force-pushed the password-protected-public-share branch from f9bbf83 to 589bca9 Compare February 20, 2026 06:48
@benjaminVadon benjaminVadon marked this pull request as ready for review February 20, 2026 06:48
@benjaminVadon benjaminVadon force-pushed the password-protected-public-share branch from 589bca9 to 2b01b20 Compare February 25, 2026 12:20
Comment on lines +466 to +469
fun String.appendQuery(query: String): String {
val querySeparator = if (contains("?")) "&" else "?"
return this + querySeparator + query
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use Uri.Builder or buildUrl from ktor to manage all the case of ApiRoutes ?

Comment on lines 358 to 366
with(publicShareViewModel) {
val emptyFilesResult = PublicShareFilesResult(files = emptyList(), shouldUpdate = false, isNewSort = false)
childrenLiveData.value = emptyFilesResult
cancelDownload()

if (folderId == ROOT_SHARED_FILE_ID || rootSharedFile.value == null) {
if (folderId == ROOT_SHARED_FILE_ID || !isFolder()) {
downloadPublicShareRootFile()
} else {
getFiles(folderId, fileListViewModel.sortType, isNewSort)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be all moved in a method in PublicShareViewModel, what do you think ?

@FabianDevel FabianDevel force-pushed the password-protected-public-share branch from 2b01b20 to eaec8d5 Compare March 2, 2026 14:04
@tevincent tevincent added the rebase Add this label to rebase the PR label Mar 6, 2026
@github-actions github-actions bot removed the rebase Add this label to rebase the PR label Mar 6, 2026
@github-actions github-actions bot force-pushed the password-protected-public-share branch from eaec8d5 to a9b8f4d Compare March 6, 2026 12:46
@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 6, 2026

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enables opening password-protected public shares directly in the Android app by adding a password validation flow that retrieves and propagates a share-link auth token across public-share API calls and file operations.

Changes:

  • Replace the “not supported” password screen with an in-app password submission/validation flow and navigation into the public share file list.
  • Introduce a PublicShareToken response model and propagate sharelink_token through public-share API routes (listing, counts, archives, previews/downloads).
  • Attach public-share auth context (UUID + token) to File objects so downstream features (thumbnails, previews, downloads, counts) can work for protected shares.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
app/src/main/res/values/strings.xml Removes obsolete “password not supported” string.
app/src/main/res/values-it/strings.xml Same string removal + copyright year bump.
app/src/main/res/values-fr/strings.xml Same string removal + copyright year bump.
app/src/main/res/values-es/strings.xml Same string removal + copyright year bump.
app/src/main/res/values-de/strings.xml Same string removal + copyright year bump.
app/src/main/res/layout/fragment_public_share_password.xml Updates UI copy/visibility/button label for password entry & validation.
app/src/main/java/com/infomaniak/drive/utils/PreviewUtils.kt Uses safer query appending for OnlyOffice PDF downloads.
app/src/main/java/com/infomaniak/drive/ui/publicShare/PublicShareViewModel.kt Adds token-aware init/root file fetch/listing/import/archive behavior; tracks rootFileId.
app/src/main/java/com/infomaniak/drive/ui/publicShare/PublicSharePasswordFragment.kt Implements in-app password validation and navigates to list on success.
app/src/main/java/com/infomaniak/drive/ui/publicShare/PublicShareMultiSelectActionsBottomSheetDialog.kt Adds token to public-share archive download URL generation.
app/src/main/java/com/infomaniak/drive/ui/publicShare/PublicShareListFragment.kt Updates navigation and root file handling for password-protected entry flow.
app/src/main/java/com/infomaniak/drive/ui/publicShare/PublicShareActivity.kt Initializes rootFileId from navigation args.
app/src/main/java/com/infomaniak/drive/ui/fileList/FileListViewModel.kt Adds token to public-share file count requests.
app/src/main/java/com/infomaniak/drive/ui/LaunchActivity.kt Copyright year bump only.
app/src/main/java/com/infomaniak/drive/data/models/File.kt Adds publicShareAuthToken field (non-persisted/non-parceled).
app/src/main/java/com/infomaniak/drive/data/api/publicshare/PublicShareToken.kt New parcelable model for password auth response token.
app/src/main/java/com/infomaniak/drive/data/api/publicshare/PublicShareApiRepository.kt Adds token support across public-share endpoints and centralizes URL token injection.
app/src/main/java/com/infomaniak/drive/data/api/ApiRoutes.kt Adds token-aware public-share URL helpers + appendQuery utility.
app/src/main/java/com/infomaniak/drive/MatomoDrive.kt Adds ValidatePassword tracking event and removes unused OpenInBrowser.
Comments suppressed due to low confidence (1)

app/src/main/java/com/infomaniak/drive/ui/publicShare/PublicSharePasswordFragment.kt:95

  • observeSubmitPasswordResult treats any empty token as a wrong password and clears the field. With the current ViewModel behavior, an empty token also happens on network/server errors, so users will get a “wrong password” message in those cases. Consider observing a richer result (token + error) and showing an appropriate error message when the auth call fails for reasons other than an invalid password.
        publicShareViewModel.submitPasswordResult.observe(viewLifecycleOwner) { authToken ->
            if (authToken.isNotEmpty()) {
                publicShareViewModel.hasBeenAuthenticated = true
                publicShareViewModel.initPublicShare(authToken)
            } else {
                passwordValidateButton.hideProgressCatching(R.string.buttonValid)
                publicSharePasswordEditText.text = null
                publicSharePasswordLayout.error = getString(R.string.errorWrongPassword)
            }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +324 to +333
fun getPublicShareChildrenFiles(
driveId: Int,
linkUuid: String,
fileId: Int,
sortType: SortType,
authToken: String?,
): String {
val orderQuery = "order_by=${sortType.orderBy}&order=${sortType.order}"
return "$SHARE_URL_V3/$driveId/share/$linkUuid/files/$fileId/files?$sharedFileWithQuery&$orderQuery"
val authParam = authToken?.let { "&sharelink_token=$it" } ?: ""
return "$SHARE_URL_V3/$driveId/share/$linkUuid/files/$fileId/files?$sharedFileWithQuery&$orderQuery$authParam"
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getPublicShareChildrenFiles appends sharelink_token directly into the URL without URL-encoding. If the token contains reserved characters, the resulting URL can be invalid or interpreted incorrectly. Prefer building the query via appendQuery(...) (with proper encoding) or using an HttpUrl builder; also avoid appending when the token is blank.

Copilot uses AI. Check for mistakes.
Comment on lines +340 to +357
private fun getPublicShareFileThumbnail(driveId: Int, linkUuid: String, fileId: Int, authToken: String? = null): String {
val authParam = authToken?.let { "?sharelink_token=$it" } ?: ""
return "${publicShareFile(driveId, linkUuid, fileId)}/thumbnail$authParam"
}

private fun getPublicShareFilePreview(driveId: Int, linkUuid: String, fileId: Int): String {
return "${publicShareFile(driveId, linkUuid, fileId)}/preview"
private fun getPublicShareFilePreview(driveId: Int, linkUuid: String, fileId: Int, authToken: String? = null): String {
val authParam = authToken?.let { "?sharelink_token=$it" } ?: ""
return "${publicShareFile(driveId, linkUuid, fileId)}/preview$authParam"
}

private fun downloadPublicShareFile(driveId: Int, linkUuid: String, fileId: Int): String {
return "${publicShareFile(driveId, linkUuid, fileId)}/download"
private fun downloadPublicShareFile(driveId: Int, linkUuid: String, fileId: Int, authToken: String? = null): String {
val authParam = authToken?.let { "?sharelink_token=$it" } ?: ""
return "${publicShareFile(driveId, linkUuid, fileId)}/download$authParam"
}

private fun showPublicShareOfficeFile(driveId: Int, linkUuid: String, fileId: Int): String {
// For now, this call fails because the back hasn't dev the conversion of office files to pdf for mobile
return "$SHARE_URL_V1/share/$driveId/$linkUuid/preview/text/$fileId"
private fun showPublicShareOfficeFile(driveId: Int, linkUuid: String, fileId: Int, authToken: String? = null): String {
val authParam = authToken?.let { "?sharelink_token=$it" } ?: ""
return "$SHARE_URL_V1/share/$driveId/$linkUuid/preview/text/$fileId$authParam"
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several helpers (getPublicShareFileThumbnail, getPublicShareFilePreview, downloadPublicShareFile, showPublicShareOfficeFile, downloadPublicShareArchive) build ?sharelink_token=$it via raw string concatenation. This should URL-encode the token and skip appending when it’s blank to avoid malformed URLs and accidental “empty token” requests.

Copilot uses AI. Check for mistakes.
driveId = driveId,
publicShareUuid = publicShareUuid,
archiveUuid = it.uuid,
authToken = submitPasswordResult.value,
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

downloadPublicShareArchive is passed submitPasswordResult.value directly. Since this value can be an empty string, this may generate URLs containing ?sharelink_token= (empty token), which can lead to avoidable backend errors. Consider passing submitPasswordResult.value?.takeIf { it.isNotBlank() } (or storing the token as nullable state) so the token is only included when present.

Suggested change
authToken = submitPasswordResult.value,
authToken = submitPasswordResult.value?.takeIf { token -> token.isNotBlank() },

Copilot uses AI. Check for mistakes.
Comment on lines 103 to +110
fun submitPublicSharePassword(password: String) = viewModelScope.launch {
val passwordResult = PublicShareApiRepository.submitPublicSharePassword(
val token = PublicShareApiRepository.submitPublicSharePassword(
driveId = driveId,
linkUuid = publicShareUuid,
password = password,
).data
).data?.token ?: ""

submitPasswordResult.postValue(passwordResult)
submitPasswordResult.postValue(token)
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

submitPublicSharePassword posts an empty token for any non-success response (network/server error included). The UI then treats an empty token as a wrong password, which is misleading and can block access when the password is correct but the request failed. Consider propagating the API error separately (e.g., a sealed result or Pair<ApiError?, String?>) and only emitting a token when the response is successful; also avoid using an empty string to represent “no token” (prefer null or takeIf { it.isNotBlank() }).

Copilot uses AI. Check for mistakes.
val apiResponse = PublicShareApiRepository.getPublicShareInfo(driveId, publicShareUuid)
fun initPublicShare(authToken: String? = null) = viewModelScope.launch {
val apiResponse = PublicShareApiRepository.getPublicShareInfo(driveId, publicShareUuid, authToken)
val result = if (apiResponse.isSuccess()) null to apiResponse.data else apiResponse.error to null
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initPublicShare treats any isSuccess() response as success even if data is null, which can lead to navigating with PUBLIC_SHARE_DEFAULT_ID (e.g., shareLink?.fileId ?: PUBLIC_SHARE_DEFAULT_ID) and subsequent invalid API calls. Consider treating “success with null body” as an error (post a non-null error / generic error) to prevent proceeding without a valid ShareLink.

Suggested change
val result = if (apiResponse.isSuccess()) null to apiResponse.data else apiResponse.error to null
val result = if (apiResponse.isSuccess() && apiResponse.data != null) {
null to apiResponse.data
} else {
(apiResponse.error ?: ApiError()) to null
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request feature A new functionality is added to the product

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants